package com.xuecheng.auth.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xuecheng.framework.client.XcServiceList;
import com.xuecheng.framework.domain.ucenter.ext.AuthToken;
import com.xuecheng.framework.domain.ucenter.ext.UserToken;
import com.xuecheng.framework.domain.ucenter.response.AuthCode;
import com.xuecheng.framework.exception.ExceptionCast;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;


import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class AuthService {
    //token存储到cookie中的过期时间
    @Value("${auth.tokenValiditySeconds}")
    long tokenValiditySeconds;
    @Autowired
    RestTemplate restTemplate;
    @Autowired
    LoadBalancerClient loadBalancerClient;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    //认证方法
    public AuthToken login(String username, String password, String clientId, String clientSecret) {
        //申请令牌
        AuthToken authToken=applyToken(username,password,clientId,clientSecret);
        if(authToken==null){
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
        }
        //将令牌存储到redis中
        String jti_token = authToken.getJti_token();
        String jsonString = JSON.toJSONString(authToken);
        boolean saveTokenResult=saveToken(jti_token,jsonString,tokenValiditySeconds);
        if(!saveTokenResult){
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_TOKEN_SAVEFAIL);
        }
        return authToken;
    }
    //存储令牌到redis中
    private boolean saveToken(String jti_token, String jsonString,long ttl) {
            //令牌名称
        String name ="user_token: "+jti_token;
        //保存到redis中
        stringRedisTemplate.boundValueOps(name).set(jsonString,ttl, TimeUnit.SECONDS);
        Long expire = stringRedisTemplate.getExpire(name);
        return expire>0;
    }

    //申请令牌
    private AuthToken applyToken(String username, String password, String clientId, String clientSecret) {
        //选中认证服务的地址
        ServiceInstance serviceInstance = loadBalancerClient.choose(XcServiceList.XC_SERVICE_UCENTER_AUTH);
        if(serviceInstance==null){
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_AUTHSERVER_NOTFOUND);
        }
        //获取令牌的url
        String path=serviceInstance.getUri().toString()+"/auth/oauth/token";
        //定义body
        MultiValueMap<String,String> formData=new LinkedMultiValueMap<>();
        //授权方式
        formData.add("grant_type","password");
        //账户
        formData.add("username",username);
        //密码
        formData.add("password",password);
        MultiValueMap<String,String> header=new LinkedMultiValueMap<>();
        header.add("Authorization",httpbasic(clientId,clientSecret));
        //指定 restTemplate当遇到400或401响应时候也不要抛出异常，也要正常返回值
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
                //当响应的值为400或401时候也要正常响应，不要抛出异常
                if(response.getRawStatusCode()!=400 && response.getRawStatusCode()!=401){
                    super.handleError(response);
                }
            }
        });
        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(formData, header);
        //http请求spring security的申请令牌接口
            ResponseEntity<Map> responseEntity = restTemplate.exchange(path, HttpMethod.POST,entity , Map.class);
           Map map=responseEntity.getBody();
        if(map == null ||
                map.get("access_token") == null ||
                map.get("refresh_token") == null ||
                map.get("jti") == null){//jti是jwt令牌的唯一标识作为用户身份令牌
            //获取spring security返回的错误信息
            String error_description = (String) map.get("error_description");
            if(StringUtils.isNotEmpty(error_description)){
                if(error_description.equals("坏的凭证")){
                    ExceptionCast.cast(AuthCode.AUTH_CREDENTIAL_ERROR);
                }else  if(error_description.indexOf("UserDetailsService returned null")>0){
                    ExceptionCast.cast(AuthCode.AUTH_ACCOUNT_NOTEXISTS);
                }
            }
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
        }
        AuthToken authToken = new AuthToken();
        //访问令牌
        String access_token = (String) map.get("access_token");
        String refresh_token = (String) map.get("refresh_token");
        String jti_token = (String) map.get("jti");
        authToken.setJti_token(jti_token);
        authToken.setJwt_token(access_token);
        authToken.setRefresh_token(refresh_token);
        return authToken;
    }
    //获取httpbasic认证字符串
    private String httpbasic(String clientId,String clientSecret){
        //将客户端id和客户端密码拼接，按“客户端id:客户端密码”
        String string=clientId+":"+clientSecret;
        //进行base64编码
        byte[] encode = Base64.encode(string.getBytes());
        return "Basic "+new String(encode);
    }
    //根据身份令牌获取redis中的令牌信息
    public AuthToken getUserToken(String uid) {
        //拼装redis中的key
        String key="user_token: "+uid;
        String s = stringRedisTemplate.opsForValue().get(key);
        //获取令牌信息
        try {
            AuthToken authToken = JSON.parseObject(s, AuthToken.class);
            return authToken;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    //从rides中删除令牌信息
    public void delToken(String uid) {
        //拼装redis中的key
        String key = "user_token: "+uid;
        stringRedisTemplate.delete(key);
    }
}
